#define _ATFILE_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include "libcytovision.h"

int cytovision_check_metaphase(struct st_img *current_img) {
  uint32_t header;

  memcpy(&header,current_img->data->header,sizeof(uint32_t));
  header=ntohl(header);

  if(header!=HEADER_METAPHASE) {
    cytovision_debug(0,"INVALID MAGIC TYPE 0x%08x FOR METAPHASE (0x%08x)\n",header,HEADER_METAPHASE);
    return -1;
  } else {
    cytovision_debug(9,"METAPHASE IMAGE=%s\n",current_img->name);
  }

  if(current_img->data->obj_count<current_img->data->obj_count2) {
    cytovision_debug(0,"Invalid object counting (%u of %u)",
      current_img->data->obj_count,
      current_img->data->obj_count2);
    return -1;
  }

  printf("OBJS=%u/%u (%02x)\n",
    current_img->data->obj_count,
    current_img->data->obj_count2,
    current_img->data->ukn2);
  return 0;
}

int cytovision_load_images(struct st_cell *cell) {
  struct dirent *ent;
  struct st_img *current_img;
  uint32_t header;
  char *ext;

  cell->num_imgs=0;

  rewinddir(cell->fd);
  while((ent=readdir(cell->fd))) {
    if(strcmp(ent->d_name,".")==0) continue;
    if(strcmp(ent->d_name,"..")==0) continue;
    if(strcmp(ent->d_name,CELLNAME)==0) continue;
    if(strcmp(ent->d_name,CGHSTATUS)==0) continue;
    if((ext=rindex(ent->d_name,'.'))==NULL) continue;
    ext++;

    if((cell->img[cell->num_imgs]=malloc(sizeof(struct st_img)))==NULL) {
      cytovision_debug(0,"Not enough memory\n");
      return -1;
    }
    current_img=cell->img[cell->num_imgs];
    cell->num_imgs++;
    bzero(current_img,sizeof(struct st_img));
    current_img->ext=ext;

    // Alloc string for the name of image file
    if((current_img->name=strdup(ent->d_name))==NULL) return -1;

    if((current_img->fd=openat(dirfd(cell->fd),current_img->name,O_RDONLY))==-1) {
      cytovision_debug(0,"Cannot open image %s\n",current_img->name);
      return -1;
    }
    fstat(current_img->fd,&current_img->st);
    current_img->data=malloc(current_img->st.st_size);
    read(current_img->fd,current_img->data,current_img->st.st_size);
    memcpy(&header,current_img->data->header,sizeof(uint32_t));
    header=ntohl(header);

    if(strcmp(current_img->ext,"kar")==0) {
      if(header!=HEADER_KARYOTYPE) {
        cytovision_debug(0,"INVALID MAGIC TYPE 0x%08x FOR KARYOTYPE (0x%08x)\n",header,HEADER_KARYOTYPE);
        return -1;
      } else {
        cytovision_debug(9,"KARYOTYPE IMAGE=%s\n",ent->d_name);
      }
    } else if(strcmp(current_img->ext,"met")==0) {
      if(cytovision_check_metaphase(current_img)==-1) return -1;
    } else if(strcmp(current_img->ext,"fuse")==0) {
      if(cytovision_check_metaphase(current_img)==-1) return -1;
    } else if(strcmp(current_img->ext,"probe")==0) {
      if(header!=HEADER_PROBE) {
        cytovision_debug(0,"INVALID MAGIC TYPE 0x%08x FOR PROBE (0x%08x)\n",header,HEADER_PROBE);
        return -1;
      } else {
        cytovision_debug(9,"PROBE IMAGE=%s\n",ent->d_name);
      }
    } else if(strcmp(current_img->ext,"flex")==0) {
      if(header!=HEADER_FLEX) {
        cytovision_debug(0,"INVALID MAGIC TYPE 0x%08x FOR FLEX (0x%08x)\n",header,HEADER_FLEX);
        return -1;
      } else {
        cytovision_debug(9,"FLEX IMAGE=%s\n",ent->d_name);
      }
    } else if(strcmp(current_img->ext,"cgh")==0) {
      cytovision_debug(9,"CGH IMAGE=%s\n",ent->d_name);
    } else if(strcmp(current_img->ext,"rats")==0) {
      cytovision_debug(9,"RATS IMAGE=%s\n",ent->d_name);
    } else if(strcmp(current_img->ext,"raw")==0) {
      cytovision_debug(9,"RAW IMAGE=%s\n",ent->d_name);
    } else {
      cytovision_debug(0,"UNKNOWN FILE=%s\n",ent->d_name);
      printf("HEADER=%02x %02x %02x %02x\n",
        current_img->data->header[0],
        current_img->data->header[1],
        current_img->data->header[2],
        current_img->data->header[3]);
    }
  }
  return 0;
}

void cytovision_close_image(struct st_img *img) {
    if(img->name) free(img->name);
    if(img->fd!=-1) close(img->fd);
    free(img);
}

void cytovision_close_images(struct st_cell *cell) {
    int a;

    for(a=0;a<cell->num_imgs;a++) {
      cytovision_close_image(cell->img[a]);
    }
    cell->num_imgs=0;
}

int cytovision_load_cells(struct st_slide *slide) {
  struct dirent *ent;
  int len;
  struct st_cell *current_cell;

  slide->num_cells=0;
  rewinddir(slide->fd);
  while((ent=readdir(slide->fd))) {
    if(slide->num_cells==MAXNUMCELLS-1) {
      cytovision_debug(1,"Max number of %u cells reached\n",MAXNUMCELLS);
      continue;
    }
    if(strncmp(ent->d_name,"cell",4)!=0) continue;
    if((slide->cells[slide->num_cells]=malloc(sizeof(struct st_cell)))==NULL) return -1;
    current_cell=slide->cells[slide->num_cells];
    slide->num_cells++;
    bzero(current_cell,sizeof(struct st_cell));

    // Alloc string for cell dir
    len=strlen(slide->dir);
    if((current_cell->dir=malloc(len+strlen(ent->d_name)+2))==NULL) return -1;

    strcpy(current_cell->dir,slide->dir);
    current_cell->dir[len]='/';
    strcpy(&current_cell->dir[len+1],ent->d_name);

    if((current_cell->fd=opendir(current_cell->dir))==NULL) return -1;

    if((current_cell->fdname=openat(dirfd(current_cell->fd),CELLNAME,O_RDONLY))==-1) return -1;

    // Get slide name
    if((len=read(current_cell->fdname,&current_cell->name,16))==-1) return -1;
    current_cell->name[len]=0;

    cytovision_debug(9,"CELL=%s : %s\n",slide->name,current_cell->name);

    cytovision_load_images(current_cell);
  }
  return 0;
}

void cytovision_close_cells(struct st_slide *slide) {
  int a;

  for(a=0;a<slide->num_cells;a++) {
    cytovision_close_images(slide->cells[a]);
    if(slide->cells[a]->fd!=NULL) closedir(slide->cells[a]->fd);
    if(slide->cells[a]->fdname!=-1) close(slide->cells[a]->fdname);
    free(slide->cells[a]->dir);
    free(slide->cells[a]);
  }
}

int cytovision_load_slides(struct st_caseinfo *caseinfo) {
  struct dirent *ent;
  int len;
  struct st_slide *current_slide;

  caseinfo->num_slides=0;
  rewinddir(caseinfo->fdcasedir);

  while((ent=readdir(caseinfo->fdcasedir))) {
    if(caseinfo->num_slides==MAXNUMSLIDES-1) {
      cytovision_debug(1,"Max number of %u slides reached\n",MAXNUMSLIDES);
      continue;
    }
    if(strncmp(ent->d_name,"slide",5)!=0) continue;
    if(ent->d_type!=DT_DIR) continue;
    if((caseinfo->slides[caseinfo->num_slides]=malloc(sizeof(struct st_slide)))==NULL) return -1;

    current_slide=caseinfo->slides[caseinfo->num_slides];

    // Alloc string for slide dir
    len=strlen(caseinfo->casedir);
    if((current_slide->dir=malloc(len+strlen(ent->d_name)+2))==NULL) return -1;

    strcpy(current_slide->dir,caseinfo->casedir);
    current_slide->dir[len]='/';
    strcpy(&current_slide->dir[len+1],ent->d_name);

    caseinfo->num_slides++;

    if((current_slide->fd=opendir(current_slide->dir))==NULL) return -1;

    // Alloc string for .NAME file
    len=strlen(current_slide->dir);
    if((current_slide->namedir=malloc(len+strlen(SLIDENAME)+2))==NULL) return -1;

    strcpy(current_slide->namedir,current_slide->dir);
    current_slide->namedir[len]='/';
    strcpy(&current_slide->namedir[len+1],SLIDENAME);

    if((current_slide->fdname=open(current_slide->namedir,O_RDONLY))==-1) return -1;

    // Get slide name
    if((len=read(current_slide->fdname,&current_slide->name,16))==-1) return -1;
    current_slide->name[len]=0;

    if(cytovision_load_cells(current_slide)==-1) return -1;
  }
  return 0;
}

void cytovision_close_slide(struct st_slide *slide) {
    cytovision_close_cells(slide);
    if(slide->fd!=NULL) closedir(slide->fd);
    if(slide->fdname!=-1) close(slide->fdname);
    free(slide->dir);
    free(slide->namedir);
    free(slide);
}

void cytovision_close_slides(struct st_caseinfo *caseinfo) {
  int a;

  for(a=0;a<caseinfo->num_slides;a++) {
    cytovision_close_slide(caseinfo->slides[a]);
  }
  caseinfo->num_slides=0;
}

int cytovision_load_casedetail(struct st_caseinfo *caseinfo) {
  int size;
  int itemnum;
  char magic;

  // Open FD descriptor for case.detail
  if((caseinfo->fdcasedetail=openat(dirfd(caseinfo->fdcasedir),CASEDETAIL,O_RDONLY))==-1) {
    cytovision_close_case(caseinfo);
    return -1;
  }

  // Look by magic number
  if(read(caseinfo->fdcasedetail,&magic,1)!=1) {
    caseinfo->casecorrupt=-1;
    return 0;
  }
  if(magic!=0x1B) {
    caseinfo->casecorrupt=-1;
    return 0;
  }
  
  for(itemnum=0;;itemnum++) {
    // Get size of next token (if any);
    if(read(caseinfo->fdcasedetail,&size,2)!=2) {
      caseinfo->casedetailnumitems=itemnum;
      return 0;
    }

    // Skip any empty fields
    if(size==0) continue;

    // Alloc memory
    if((caseinfo->casedetailitems[itemnum]=malloc(size+1))==NULL) {
      cytovision_close_case(caseinfo);
      return -1;
    }

    // Read next token
    if(read(caseinfo->fdcasedetail,caseinfo->casedetailitems[itemnum],size)==-1) {
      free(caseinfo->casedetailitems[itemnum]);
      caseinfo->casedetailnumitems=itemnum;
      caseinfo->casecorrupt=-1;
      return 0;
    }
    caseinfo->casedetailitems[itemnum][size]=0; // Put a NULL-terminator
    cytovision_debug(9,"ITEM=%s\n",caseinfo->casedetailitems[itemnum]);
  }
  return 0;
}

void cytovision_close_casedetail(struct st_caseinfo *caseinfo) {
  int a;

  for(a=0;a<caseinfo->casedetailnumitems;a++)
    free(caseinfo->casedetailitems[a]);
  if(caseinfo->fdcasedetail!=-1) close(caseinfo->fdcasedetail);
}

/*! \brief Open case folder.
 * \param path Directory where the case can be found
 * \param casename String containing the case name
 *
 * Open folder containing all case information and images
 *
 * \return st_caseinfo struct
 * \retval NULL Error opening case
*/

struct st_caseinfo *cytovision_open_case(char *path,char *casename) {
  struct st_caseinfo *caseinfo;
  int len;
  int desl;

  if((caseinfo=malloc(sizeof(struct st_caseinfo)))==NULL) return NULL;
  bzero(caseinfo,sizeof(struct st_caseinfo));

  if(cytovision_check_security(casename)==-1) {
    return NULL;
  }

  // Alloc casedir string
  len=strlen(path);
  if((caseinfo->casedir=malloc(len+strlen(casename)+2))==NULL) {
    cytovision_close_case(caseinfo);
    return NULL;
  }
  strcpy(caseinfo->casedir,path);
  desl=0; // Deslocation if a '/' exist or not on end
  if(caseinfo->casedir[len-1]!='/') {
    caseinfo->casedir[len]='/';
    desl=1;
  }
  strcpy(&caseinfo->casedir[len+desl],casename);

  // Open FD descriptor from DIR
  if((caseinfo->fdcasedir=opendir(caseinfo->casedir))==NULL) {
    perror(caseinfo->casedir);
    cytovision_close_case(caseinfo);
    return NULL;
  }

  if(cytovision_load_casedetail(caseinfo)) return NULL;
  if(cytovision_load_slides(caseinfo)) {
    cytovision_close_case(caseinfo);
    return NULL;
  }

  if(caseinfo->casecorrupt)
     cytovision_debug(0,"%s:*** CORRUPTED ***\n",casename);

  len=strlen(caseinfo->casedir);

  // Alloc completedir string
  if((caseinfo->casecompletedir=malloc(len+strlen(CASECOMPLETE)+2))==NULL) {
    cytovision_close_case(caseinfo);
    return NULL;
  }
  strcpy(caseinfo->casecompletedir,caseinfo->casedir);
  caseinfo->casecompletedir[len]='/';
  strcpy(&caseinfo->casecompletedir[len+1],CASECOMPLETE);

  // Open FD descriptor for COMPLETE and define if is completed or not
  caseinfo->casecomplete='n';
  if((caseinfo->fdcasecomplete=open(caseinfo->casecompletedir,O_RDONLY))!=-1) {
    read(caseinfo->fdcasecomplete,&caseinfo->casecomplete,1);
    if((caseinfo->casecomplete!='y')&&(caseinfo->casecomplete!='Y'))
      caseinfo->casecomplete='n';
  }

  // Alloc lockdir string
  if((caseinfo->lockdir=malloc(len+strlen(CASELOCK)+2))==NULL) {
    cytovision_close_case(caseinfo);
    return NULL;
  }
  strcpy(caseinfo->lockdir,caseinfo->casedir);
  caseinfo->lockdir[len]='/';
  strcpy(&caseinfo->lockdir[len+1],CASELOCK);

  cytovision_debug(1,"OPENED %s (%c)\n",casename,caseinfo->casecomplete);
  return caseinfo;
}

/*! \brief Lock case folder.
 * \param caseinfo Case structure
 * \retval 0 Succeful locked
 * \retval -1 Cannot lock
*/
int cytovision_lock_case(struct st_caseinfo *caseinfo) {
  if((caseinfo->fdlock=creat(caseinfo->lockdir,0770))==-1) {
    caseinfo->locked=0;
    return -1;
  }
  caseinfo->locked=-1;
  return 0;
}

/*! \brief Unlock case folder.
 * \param caseinfo Case structure
 * \retval 0 Succeful unlocked
 * \retval -1 Cannot unlock
*/
int cytovision_unlock_case(struct st_caseinfo *caseinfo) {
  if(unlink(caseinfo->lockdir)==-1) return -1;

  if((caseinfo->fdlock!=-1)&&(caseinfo->locked==-1)) {
    close(caseinfo->fdlock);
    caseinfo->locked=0;
  }
  return 0;
}

/*! \brief Close case folder.
 * Free all allocated resources and close all filedescriptors in use
 * for this case.
 * \param caseinfo Case structure
*/
void cytovision_close_case(struct st_caseinfo *caseinfo) {
  if(caseinfo==NULL) return;

  cytovision_close_slides(caseinfo);
  cytovision_close_casedetail(caseinfo);

  if(caseinfo->casedir) free(caseinfo->casedir);
  if(caseinfo->fdcasedir) closedir(caseinfo->fdcasedir);
  if(caseinfo->casecompletedir) free(caseinfo->casecompletedir);
  if(caseinfo->fdcasecomplete!=-1) close(caseinfo->fdcasecomplete);
  if(caseinfo->lockdir) free(caseinfo->lockdir);
  if(caseinfo->fdlock!=-1) close(caseinfo->fdlock);
  free(caseinfo);
}
